// Copyright 2023 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <thread>
#include <system.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/un.h>
#include <netinet/in.h>
#include "../cae_common/CaeLogConfig.h"
#include "../cae_agent/CaeSocketAgent.h"
#include "../cae_agent/CaeAgent.h"
#include "CaeImeClient.h"

const static std::string UNIX_IME_IPC_NAME = "CloudPhoneImeChannel";
const static uint32_t MAX_INTERVAL = 10000;
void ImeMsgHandler(void *para);
void InputMsgHandler(void *para, bool *isTaskRun);

CaeImeClient::CaeImeClient()
{
    mSockFd = -1;
    mIsTaskRun = false;
}

CaeImeClient::~CaeImeClient()
{
    if (mSockFd > 0) {
        ::close(mSockFd);
        mSockFd = -1;
    }
}

int CaeImeClient::StartProcess()
{
    if (!mIsTaskRun) {
        mConnectState = true;
        mIsTaskRun = true;
        mImeThread = std::thread(ImeMsgHandler, static_cast<void*>(this));
    }
    return 0;
}

int CaeImeClient::StopProcess()
{
    if (mIsTaskRun) {
        mIsTaskRun = false;
        mImeThread.join();
    }
    return 0;
}

int CaeImeClient::Connect()
{
    struct sockaddr_un sockAddr;
    if (mSockFd > 0) {
        close(mSockFd);
        mSockFd = -1;
    }
    mSockFd = socket(PF_UNIX, SOCK_STREAM, 0);
    if (mSockFd < 0) {
        LOGE("Connect failed, mSockFd [%d]", mSockFd);
        mSockFd = -1;
        return -1;
    }

    memset_s(&sockAddr, sizeof(sockAddr), 0, sizeof(sockAddr));
    sockAddr.sun_family = AF_LOCAL;
    sockAddr.sun_path[0] = 0;
    const int len = sizeof(sockAddr.sun_path) - 1;
    if (EOK != strncpy_s(sockAddr.sun_path + 1, len, UNIX_IME_IPC_NAME.c_str(), UNIX_IME_IPC_NAME.length())) {
        LOGE("Connect unix ime ipc failed.");
        return -1;
    }
    socklen_t alen = UNIX_IME_IPC_NAME.length() + offsetof(struct sockaddr_un, sun_path) + 1;
    if (::connect(mSockFd, (struct sockaddr *) &sockAddr, alen) < 0) {
        ::close(mSockFd);
        mSockFd = -1;
        return -1;
    }
    return 0;
}

int CaeImeClient::WriteN(uint8_t *data, const int length)
{
    int nWriteBytes = 0;
    int nBytes = 0;

    while (nWriteBytes < length) {
        nBytes = ::send(mSockFd, data, length, 0);
        if (nBytes > 0) {
            nWriteBytes += nBytes;
        } else {
            if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
                continue;
            } else {
                return -1;
            }
        }
    }
    return nWriteBytes;
}

int CaeImeClient::ReadN(uint8_t *buf, int length)
{
    int nRecvBytes = length;
    int nBytes = 0;
    int retryTimes = 0;

    memset_s(buf, length, 0, length);

    while (length > 0) {
        nBytes = ::recv(mSockFd, buf, length, 0);
        if (nBytes > 0) {
            length -= nBytes;
            buf += nBytes;
            retryTimes = 0;
        } else {
            retryTimes ++;
            if (retryTimes > 3) {
                LOGE("Retry time is too large.");
                return -1;
            }
            if (errno == EAGAIN || errno == EINTR || errno == EWOULDBLOCK) {
                continue;
            } else {
                return -1;
            }
        }
    }
    return nRecvBytes;
}

bool CaeImeClient::GetConnectState()
{
    return mConnectState;
}

bool CaeImeClient::Handshake()
{
    uint8_t buf[128] = {0};
    int iRet = ReadN(buf, IME_MSG_HEADER_LEN);
    if (iRet != IME_MSG_HEADER_LEN) {
        return false;
    }
    CaeImePktHeader_t pktHeader;
    pktHeader.type = buf[0];
    pktHeader.length = ((buf[1] << 8) | (buf[2]));
    if (pktHeader.type != IME_HANDSHAKE_PKT || pktHeader.length != 2) {
        return false;
    }
    iRet = ReadN(buf + 3, pktHeader.length);
    if (iRet != pktHeader.length) {
        return false;
    }
    int ackCode = ((buf[3] << 8) | buf[4]) << 1;
    buf[0] = IME_HANDSHAKE_ACK_PKT;
    buf[1] = 0;
    buf[2] = 2;
    buf[3] = ackCode >> 8;
    buf[4] = ackCode & 0xFF;
    iRet = WriteN(buf, IME_HANDSHAKE_ACK_PKT);
    if (iRet != 5) {
        return false;
    }
    return true;
}

void ImeMsgHandler(void *para)
{
    char threadname[50] = {0};
    (void) snprintf_s(threadname, sizeof(threadname), sizeof(threadname) - 1, "ImeMsgHandler");
    prctl(PR_SET_NAME, threadname);

    int nRetryTimes = 0;
    std::thread inputMsgHandlerThread;
    bool isInputMsgHandlerThreadRun = false;
    CaeImePktHeader_t pktHeader;
    bool isConnect = false;
    const uint32_t bufSize = 1021;
    uint8_t buf[1024] = {0};

    CaeImeClient *client = static_cast<CaeImeClient*>(para);
    while (client->mIsTaskRun) {
        if (!isConnect) {
            int iRet = client->Connect();
            if (iRet < 0) {
                nRetryTimes++;
                if (nRetryTimes > 10) {
                    break;
                }
                usleep(MAX_INTERVAL);
                continue;
            }
            if (!client->Handshake()) {
                isConnect = false;
                usleep(MAX_INTERVAL);
                continue;
            }
            nRetryTimes = 0;
            isConnect = true;
            isInputMsgHandlerThreadRun = true;
            inputMsgHandlerThread = std::thread(InputMsgHandler, static_cast<void *>(client),
                                                static_cast<bool *>(&isInputMsgHandlerThreadRun));
        }
        int iRet = client->ReadN(buf, IME_MSG_HEADER_LEN);
        if (iRet != IME_MSG_HEADER_LEN) {
            isConnect = false;
        } else {
            pktHeader.type = buf[0];
            pktHeader.length = ((buf[1] << 8) | buf[2]);
            if (pktHeader.length > bufSize) {
                isConnect = false;
            }
        }

        int msgLen = 0;
        uint8_t *tempBuf = nullptr;
        uint32_t bufSize = 0;
        if (isConnect && pktHeader.length > 0) {
            iRet = client->ReadN(buf + IME_MSG_HEADER_LEN, pktHeader.length);
            if (iRet != pktHeader.length) {
                isConnect = false;
            }
        }

        if (isConnect) {
            msgLen = IME_MSG_HEADER_LEN + pktHeader.length;
            bufSize = sizeof(STREAM_MSG_HEAD) + msgLen;
            tempBuf = new uint8_t[bufSize];
            if (tempBuf == nullptr) {
                isConnect = false;
            }
        }

        if (!isConnect) {
            isInputMsgHandlerThreadRun = false;
            inputMsgHandlerThread.join();
            usleep(MAX_INTERVAL);
            continue;
        }
        StreamMsgHead *msgHead = (StreamMsgHead*)tempBuf;
        msgHead->type = CAE_MSG_IME;
        msgHead->SetPayloadSize(IME_MSG_HEADER_LEN + pktHeader.length);
        msgHead->magicWord = CAE_STREAM_DELIMITER_MAGICWORD;
        msgHead->checksum = (unsigned char) GET_CAE_CHECKSUM(CAE_MSG_IME);

        if ((EOK != memcpy_s(tempBuf, sizeof(STREAM_MSG_HEAD), msgHead, sizeof(STREAM_MSG_HEAD))) ||
            (EOK != memcpy_s(tempBuf + sizeof(STREAM_MSG_HEAD), msgLen, buf, msgLen))) {
            LOGE("Send ime data to sdk failed.");
        }

        iRet = CaeSocketAgent::GetInstance().Send(tempBuf, bufSize);
        if (iRet < bufSize) {
            LOGE("Send ime msg to sdk failed.");
        }
        delete []tempBuf;
        usleep(MAX_INTERVAL);
    }
    client->mConnectState = false;
    if (isInputMsgHandlerThreadRun) {
        isInputMsgHandlerThreadRun = false;
        inputMsgHandlerThread.join();
    }
}

void InputMsgHandler(void *para, bool *isTaskRun) {
    char threadname[50] = {0};
    (void) snprintf_s(threadname, sizeof(threadname), sizeof(threadname) - 1, "InputMsgHandler");
    prctl(PR_SET_NAME, threadname);
    CaeImeClient *client = static_cast<CaeImeClient*>(para);
    CaeDataPipe *dataPipe = nullptr;
    for (auto pipe : CaeAgent::GetInstance().m_caePipes) {
        if (nullptr != pipe && pipe->m_type == CAE_MSG_IME) {
            dataPipe = pipe;
            break;
        }
    }
    if (nullptr == dataPipe) {
        LOGE("Ime datapipe is null");
        return;
    }

    while (*isTaskRun) {
        CaePipeData *pipeData = dataPipe->GetPipeData();
        if (pipeData == nullptr || pipeData->GetLen() < 0) {
            usleep(MAX_INTERVAL);
            continue;
        }

        uint16_t msgLen = pipeData->GetLen();
        if (client->WriteN(const_cast<uint8_t *>(pipeData->GetBuffer()), msgLen) != msgLen) {
            LOGE("Send text to ime failed.");
        }
        delete pipeData;
        pipeData = nullptr;
    }
}